/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_coap_udp.h"
#include <stddef.h>
#include "ola_log.h"
#include "ola_error.h"
#include "ola_def.h"
#include "ola_coap_conf.h"
#include "securec.h"
#include "ola_pal_os.h"
#include "ola_base_utils.h"
#include "ola_random.h"

/* url当前传入为NULL */
int OlaUdpInit(OlaCoapContext *ctx, const char *url, unsigned short port)
{
    if (ctx == NULL) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    if (url != NULL) {
        OlaLogError("not suport udp client now");
        return OLA_COM_ERR_NOT_SUPPORT;
    }
    int fd = OlaSocket(OLA_SOCKET_TYPE_UDP);
    if (fd < 0) {
        OlaLogWarning("create fd error");
        return OLA_SOCKET_ERR_FD_INVALID;
    }
    OlaSockaddrIn addr;
    (void)memset_s(&addr, sizeof(OlaSockaddrIn), 0, sizeof(OlaSockaddrIn));

    addr.sinFamily = OLA_SOCKET_AF_INET;
    addr.sinPort = OlaHtons(port);
    addr.sinAddr = OlaHtonl(OLA_SOCKET_INADDR_ANY);

    int ret = OlaBind(fd, (OlaSockaddr *)&addr);
    if (ret != 0) {
        (void)OlaClose(fd);
        OlaLogWarning("bind error");
        return OLA_SOCKET_ERR_BIND_FAILED;
    }

    ret = OlaSetSocketOpt(fd, OLA_SOCKET_OPTION_JOIN_MULTI_GROUP, NULL);
    if (ret != 0) {
        (void)OlaClose(fd);
        OlaLogWarning("join multi group error");
        return OLA_SOCKET_ERR_JOIN_MULTI_GROUP;
    }
    OlaLogDebug("create new udp fd[%d] prot[%u]", fd, port);
    ctx->fd = fd;
    return OLA_OK;
}

void OlaUdpDeinit(OlaCoapContext *ctx)
{
    if (ctx == NULL) {
        OlaLogWarning("invalid param");
        return;
    }
    if (ctx->fd >= 0) {
        (void)OlaClose(ctx->fd);
        ctx->fd = OLA_INVALID_FD;
    }
    return;
}

static int CoapUdpSessionInit(OlaCoapContext *ctx, OlaCoapSession *session, const OlaSockaddr *addr)
{
    (void)memset_s(session, sizeof(OlaCoapSession), 0, sizeof(OlaCoapSession));
    if (memcpy_s(&session->addr, sizeof(OlaSockaddr), addr, sizeof(OlaSockaddr)) != EOK) {
        OlaLogError("memcpy error");
        return OLA_SECUREC_ERR_MEMCOPY;
    }
    session->context = ctx;
    session->overFlag = false;
    int ret = OlaRand((unsigned char *)&session->sendMsgId, sizeof(session->sendMsgId));
    if (ret != OLA_OK) {
        OlaLogError("get random error");
        return ret;
    }
    session->recvMsgId = 0;
    ret = OlaRand((unsigned char *)&session->token, sizeof(session->token));
    if (ret != OLA_OK) {
        OlaLogError("get random error");
        return ret;
    }
    return OLA_OK;
}

static OlaCoapSession *CoapFindSession(OlaCoapContext *ctx, const OlaSockaddr *from)
{
    /* 查找会话是否已存在 */
    unsigned int index = 0;
    OlaCoapSession *pre = NULL;
    OlaCoapSession *cur = ctx->session;
    while (cur != NULL) {
        if (memcmp(from, &cur->addr, sizeof(OlaSockaddr)) == 0) {
            return cur;
        }
        pre = cur;
        cur = cur->next;
        index++;
    }

    if (index >= OLA_COAP_SERVER_SESSION_MAX_NUM) {
        OlaLogWarning("session over max num");
        return NULL;
    }

    OlaCoapSession *session = (OlaCoapSession *)OlaMalloc(sizeof(OlaCoapSession));
    if (session == NULL) {
        OlaLogError("malloc error");
        return NULL;
    }

    int ret = CoapUdpSessionInit(ctx, session, from);
    if (ret != OLA_OK) {
        OlaPutNullAfterFree((void **)&session);
        OlaLogWarning("session init error[%d]", ret);
        return NULL;
    }

    /* 插入链表 */
    if (ctx->session == NULL) {
        ctx->session = session;
    } else {
        pre->next = session;
    }
    return session;
}

OlaCoapSession *OlaUdpRead(OlaCoapContext *ctx, OlaBuffer *buf)
{
    if ((ctx == NULL) || (buf == NULL) || (ctx->fd < 0) || ctx->errFlag ||
        (buf->buf == NULL) || (buf->size == 0)) {
        OlaLogWarning("invalid param");
        return NULL;
    }

    OlaSockaddr from;
    (void)memset_s(&from, sizeof(OlaSockaddr), 0, sizeof(OlaSockaddr));
    int recvLen = OlaRecvFrom(ctx->fd, buf->buf, buf->size - 1, &from);
    if (recvLen <= 0) {
        OlaSocketErrorCode errcode = OlaGetSocketError(ctx->fd);
        if ((errcode == OLA_SOCKET_ERROR_NO_ERROR) || (errcode == OLA_SOCKET_ERROR_EAGAIN) ||
            (errcode == OLA_SOCKET_ERROR_EINTR)) {
            return NULL;
        } else {
            OlaLogError("read udp error len[%d] code[%d]", recvLen, errcode);
            if (ctx->eventHandler != NULL) {
                ctx->eventHandler(ctx, OLA_COAP_EVENT_SOCKET_READ_ERROR);
            }
            ctx->errFlag = true;
            return NULL;
        }
    }
    buf->len = recvLen;
    return CoapFindSession(ctx, &from);
}

int OlaUdpSend(OlaCoapSession *session, const OlaData *payload)
{
    if ((session == NULL) || (payload == NULL) || (session->context == NULL) || session->context->errFlag ||
        (payload->data == NULL) || (payload->len == 0) || (session->context->fd < 0)) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    int ret = OlaSendTo(session->context->fd, payload->data, payload->len, &session->addr);
    if (ret <= 0) {
        OlaSocketErrorCode errcode = OlaGetSocketError(session->context->fd);
        if ((errcode == OLA_SOCKET_ERROR_NO_ERROR) || (errcode == OLA_SOCKET_ERROR_EAGAIN) ||
            (errcode == OLA_SOCKET_ERROR_EINTR)) {
            ret = OlaSendTo(session->context->fd, payload->data, payload->len, &session->addr);
            if (ret > 0) {
                return OLA_OK;
            }
        }
        OlaLogError("send udp error len[%d] code[%d]", ret, errcode);
        if (session->context->eventHandler != NULL) {
            session->context->eventHandler(session->context, OLA_COAP_EVENT_SOCKET_READ_ERROR);
        }
        session->context->errFlag = true;
        return OLA_SOCKET_ERR_SENDTO;
    }

    return OLA_OK;
}

void OlaUdpSessionRelease(OlaCoapSession *session)
{
    OlaPutNullAfterFree((void **)&session);
}